package com.egoo.ticket.server.taskschedul.repeattimeoutscanningresolve.aspect;

import com.egoo.ticket.server.taskschedul.repeattimeoutscanningresolve.annotation.RedisLock;
import com.egoo.ticket.server.utils.common.RedisUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: Donny_dong
 * @Description: 切面
 * @Param:
 * @return:
 * @Date: 2021/1/4
 */
@Aspect
@Component
@Log4j2
public class RedisLockAspect {
	@Autowired
	RedisUtil redisUtil;
	/**最大重试次数*/
	private static final Integer MAX_RETRY_COUNT = 3;
	/**锁前缀*/
	private static final String LOCK_PRE_FIX = "lockPreFix";
	/**锁名称*/
	private static final String LOCK_KEY = "lockKey";
	/**锁过期时间*/
	private static final String TIME_OUT = "timeOut";
	/**阈值*/
	private static final int PROTECT_TIME = 2 << 11;

	/**
	 * @Author: Donny_dong
	 * @Description: 定义切点
	 * @Param:
	 * @return:
	 * @Date: 2021/1/4
	 */
	@Pointcut("@annotation(com.egoo.ticket.server.taskschedul.repeattimeoutscanningresolve.annotation.RedisLock)")
	public void redisLockAspect(){

	}

	@Around("redisLockAspect()")
	public void lockAroundAction(ProceedingJoinPoint proceeding) throws Exception{

		boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());
		if (flag){
			try {
				proceeding.proceed();
				Thread.sleep(PROTECT_TIME);
			} catch (Throwable throwable) {
				throwable.printStackTrace();
				throw new RuntimeException("分布式锁发生异常:"+throwable.getMessage(),throwable);
			}finally {
				this.deleteLock(proceeding);
			}
		}else {
			log.info("其他服务器正在执行此超时任务");
		}
	}

	/**
	 * @Author: Donny_dong
	 * @Description: 业务完成删除锁
	 * @Param:
	 * @return:
	 * @Date: 2021/1/4
	 */
	private void deleteLock(ProceedingJoinPoint proceeding) {
		Map<String, Object> annotation = this.getAnnotation(proceeding);
		String lockPreFix = (String) annotation.get(LOCK_PRE_FIX);
		String lockKey = (String) annotation.get(LOCK_KEY);
		redisUtil.delete(lockPreFix,lockKey);
	}

	/**
	 * @Author: Donny_dong
	 * @Description: 获取锁
	 * @Param:
	 * @return:
	 * @Date: 2021/1/4
	 */
	private boolean getLock(ProceedingJoinPoint proceeding, int i, long currentTimeMillis) {
		Map<String, Object> map = this.getAnnotation(proceeding);
		String lockPreFix = (String)map.get(LOCK_PRE_FIX);
		String lockKey = (String) map.get(LOCK_KEY);
		long expire = (long) map.get(TIME_OUT);
		if (StringUtils.isEmpty(lockPreFix) || StringUtils.isEmpty(lockKey)){
			throw new RuntimeException("注解RedisLock,未设置前缀,和名称");
		}
		if (redisUtil.setNx(lockPreFix,lockKey,expire)){
			return true;
		}else {
			long creatTime = redisUtil.getLockValue(lockPreFix, lockKey);
			if (currentTimeMillis - creatTime > (expire * 1000 +PROTECT_TIME)){
				i++;
				if (i > MAX_RETRY_COUNT){
					return false;
				}
				redisUtil.delete(lockPreFix,lockKey);
				getLock(proceeding,i,currentTimeMillis);
			}
			return false;
		}


	}

	/**
	 * @Author: Donny_dong
	 * @Description: 获取注解信息
	 * @Param:
	 * @return:
	 * @Date: 2021/1/4
	 */
	private Map<String,Object> getAnnotation(ProceedingJoinPoint proceeding) {
		Class<?> aClass = proceeding.getTarget().getClass();
		Method[] methods = aClass.getMethods();
		String methodName = proceeding.getSignature().getName();
		for (Method method:methods
			 ) {
			if (method.getName().equals(methodName)){
				Map<String, Object> map = new HashMap<>();
				RedisLock annotation = method.getAnnotation(RedisLock.class);
				map.put(LOCK_PRE_FIX,annotation.lockPrefix());
				map.put(LOCK_KEY,annotation.lockKey());
				map.put(TIME_OUT,annotation.timeUnit().toSeconds(annotation.timeOut()));
				return map;
			}
		}
		return null;
	}

}
